/***************************************************************
*  Copyright (c) 2007 by GroupMe! Team (www.groupme.net)
*  All rights reserved.
*
*  This file is part of the GroupMe! Project. Source code of 
*  this project is closed and redistribution of this code is
*  prohibited. 
*  
*  Contact: http://www.groupme.net
*
*  This copyright notice MUST APPEAR in all copies of the file!
***************************************************************/
package net.groupme.controller;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.groupme.api.API_CONSTANTS;
import net.groupme.exceptions.search.SearchEngineException;
import net.groupme.gui.constants.VIEW_VOCABULARY;
import net.groupme.logging.GroupMeLoggerFactory;
import net.groupme.logging.GroupMeNavigationLogger;
import net.groupme.logging.model.ActionIdentifier;
import net.groupme.model.Group;
import net.groupme.model.MODEL_CONSTANTS;
import net.groupme.model.ObjectFactory;
import net.groupme.model.Resource;
import net.groupme.model.User;
import net.groupme.search.SEARCH_CONFIGURATION;
import net.groupme.search.SearchManager;
import net.groupme.search.utils.SearchUtility;
import net.groupme.user.UserManager;
import net.groupme.utils.HTTPUtilities;
import net.groupme.utils.ResourceTools;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

/**
 * created on Jul 3, 2007
 * 
 * This GroupMe! controller handles search requests (searching for groups, resources, users,..).
 * 
 * @author Fabian Abel, <a href="mailto:abel@l3s.de">abel@l3s.de</a>
 * @author last edited by: $Author: fabian $
 * 
 * @version $Revision: 1.30 $ $Date: 2009-03-11 23:16:43 $
 */
public class SearchController implements Controller, ApplicationContextAware{


	/** Logger for this class and subclasses */
	protected final Log logger = LogFactory.getLog(getClass());
	
	
	/** the application context */
	private ApplicationContext applicationContext = null;
	
	/** GroupMe Logger */
	private GroupMeNavigationLogger glogger = GroupMeLoggerFactory.getNavigationLogger();
	
	/** Number of latest groups*/
	private int numberOfLatestGroups = 0;
	
	/**
	 * Set the ApplicationContext that this object runs in. 
	 * @param applicationContext the current {@link ApplicationContext} 
	 */
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.applicationContext = applicationContext;		
	}
	/**
	 * Get the ApplicationContext that this object runs in. 
	 * @return applicationContext the current {@link ApplicationContext} 
	 */
	public ApplicationContext getApplicationContext()  {
		return this.applicationContext;		
	}
	
	/* (non-Javadoc)
	 * @see org.springframework.web.servlet.mvc.Controller#handleRequest(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
	 */
	public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
		String userRequest = request.getRequestURI();
		boolean uriPatternResourceTagRequest = false;
		boolean uriPatternResourceRequest = userRequest.contains(VIEW_VOCABULARY.REQUEST_TYPE_RESOURCE);
		if (userRequest.contains(VIEW_VOCABULARY.REQUEST_TYPE_RESOURCE_SMALL)){
			uriPatternResourceTagRequest = true;
			uriPatternResourceRequest = false;
		}
		userRequest = userRequest.substring(userRequest.lastIndexOf("/") + 1);
		logger.info("Requested Site/Action: " + userRequest);

		//output:
    	String output = getStringValue(request, API_CONSTANTS.PARAMETER_OUTPUT);
    	if(output == null){//Standard output format is HTML:
    		output = API_CONSTANTS.OUTPUT_HTML;
    	}
    	//create the MaV:
    	ModelAndView mav = new ModelAndView();
    	//switch cases that can be handled by this controller:
    	if(request.getRequestURI().contains(VIEW_VOCABULARY.RESTful_POPULAR_GROUPS_URI_PATTERN_PARTIAL)){
    		mav = getPopularGroups(request);
    		mav.setViewName(VIEW_VOCABULARY.VIEW_WELCOME);
    		mav.addObject(VIEW_VOCABULARY.PARAMETER_POPULAR, VIEW_VOCABULARY.GROUPS);
    		//TODO: beim Aufruf von popular-Stuff passiert wieder nen Spring-Fehler:
    		//IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request? If you are actually operating within a web request and still receive this message,your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
    		//...
    		//Error creating bean with name 'userManager': Scope 'session' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request? If you are actually operating within a web request and still receive this message,your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
    	}else if(request.getRequestURI().contains(VIEW_VOCABULARY.RESTful_RANDOM_GROUPS_URI_PATTERN_PARTIAL)){
    		mav = getRandomGroups(request);
    		mav.setViewName(VIEW_VOCABULARY.VIEW_WELCOME);
    		mav.addObject(VIEW_VOCABULARY.PARAMETER_RANDOM, VIEW_VOCABULARY.GROUPS);
		}else if(request.getRequestURI().contains(VIEW_VOCABULARY.RESTful_LATEST_GROUPS_URI_PATTERN_PARTIAL)){
			//parameter pageID sent?
			if(getStringValue(request, "pageID") == null){
				//set the number of all latest groups
				setNumberOfLatestGroups();
				mav = getLatestGroups(request, 0);
	    		mav.setViewName(VIEW_VOCABULARY.VIEW_WELCOME);
	    		mav.addObject(VIEW_VOCABULARY.PARAMETER_LATEST, VIEW_VOCABULARY.GROUPS);
	    		mav.addObject("numberOfLatestGroups", getNumberOfLatestGroups());
	    		logger.info("Show page: " + 1);
				
			}else {
				
				int pageID = Integer.parseInt(request.getParameter("pageID"));
				int startPosition = pageID * 10 - 9;
				mav = getLatestGroups(request, startPosition);
	    		mav.setViewName(VIEW_VOCABULARY.VIEW_WELCOME);
	    		mav.addObject(VIEW_VOCABULARY.PARAMETER_LATEST, VIEW_VOCABULARY.GROUPS);
	    		mav.addObject("numberOfLatestGroups", getNumberOfLatestGroups());
	    		logger.info("Show page: " + pageID);
			}
    		
		}else if((VIEW_VOCABULARY.ACTION_SEARCH.equals(userRequest) && request.getParameterNames().hasMoreElements()) ||
    			request.getRequestURI().contains(VIEW_VOCABULARY.RESTful_TAG_URI_PATTERN_PARTIAL)){
    		mav = search(request, response);
    		logger.info("Search finished");
    		if(API_CONSTANTS.OUTPUT_RDF.equals(output) || API_CONSTANTS.OUTPUT_RDF.equals(HTTPUtilities.detectRequestedOutputFormat(request))){
    			mav.setViewName(API_CONSTANTS.VIEW_RDF_TAG);
    		}else{
    			mav.setViewName(VIEW_VOCABULARY.VIEW_SEARCH);
    		}
    	}
    	else if(VIEW_VOCABULARY.ACTION_VIEW_RESOURCE.equals(userRequest) || uriPatternResourceRequest){
    		mav = viewResource(request, response);
    		if(API_CONSTANTS.OUTPUT_RDF.equals(output) || API_CONSTANTS.OUTPUT_RDF.equals(HTTPUtilities.detectRequestedOutputFormat(request))){
    			mav.setViewName(API_CONSTANTS.VIEW_RDF_RESOURCE);
    		}
    	}
    	else if(VIEW_VOCABULARY.ACTION_VIEW_RESOURCE_SMALL.equals(userRequest) || uriPatternResourceTagRequest){
    		mav = viewResourceSmall(request, response);
    	}
    	else{
    		logger.info("Search not started");
    	//standard search view:
        	mav.setViewName(VIEW_VOCABULARY.VIEW_SEARCH);
    	}
    	logger.info("Indicator of result view: " + mav.getViewName());
    	return mav;
	}

	/**
	 * This method handles requests for viewing a Resource.
	 * @param request the HTTP request
	 * @param response the HTTP response
	 * @return the Spring {@link ModelAndView} object which includes also object that
	 * can be accessed by the view (e.g. the actual resource to be viewed)
	 */
	private ModelAndView viewResource(HttpServletRequest request, HttpServletResponse response) {
		//TODO: ResourceTools.... Abfrage
		String id = getStringValue(request, VIEW_VOCABULARY.PARAMETER_ID);
		ModelAndView mav = new ModelAndView();
		
		//Does the given resource exist?
		if(!ResourceTools.doesResourceWithGivenIdExists(id, false)){
			mav.setViewName(VIEW_VOCABULARY.VIEW_GENERAL_ERROR);
			return mav;
		}
		
		Resource resource = ObjectFactory.getResource(id);
		
		//begin groupme-logging:
		User user = ((UserManager) applicationContext.getBean(VIEW_VOCABULARY.USER_MANGER, UserManager.class)).getUser();
		glogger.logBrowsing(user, request.getSession().getId(), ActionIdentifier.RESOURCE_CLICK, resource);
		//end groupme-logging
		
		mav.addObject(VIEW_VOCABULARY.PARAMETER_RESOURCE, resource);
		mav.setViewName(VIEW_VOCABULARY.VIEW_RESOURCE);
		return mav;
	}

	/**
	 * This method handles requests for viewing a Resource.
	 * @param request the HTTP request
	 * @param response the HTTP response
	 * @return the Spring {@link ModelAndView} object which includes also object that
	 * can be accessed by the view (e.g. the actual resource to be viewed)
	 */
	private ModelAndView viewResourceSmall(HttpServletRequest request, HttpServletResponse response) {
		String id = getStringValue(request, VIEW_VOCABULARY.PARAMETER_ID);
		ModelAndView mav = new ModelAndView();
		//Does the given resource exist?	
		if(!ResourceTools.doesResourceWithGivenIdExists(id, false)){
			//If the Resource does not exists it could be a group
			if(ResourceTools.doesResourceWithGivenIdExists(id, true)){
				Group group = ObjectFactory.getGroup(id);
				mav.addObject(VIEW_VOCABULARY.PARAMETER_RESOURCE, group);
				mav.setViewName(VIEW_VOCABULARY.VIEW_RESOURCE_SMALL);
				return mav;
			}
			mav.setViewName(VIEW_VOCABULARY.VIEW_GENERAL_ERROR);
			return mav;
		}
		
		Resource resource = ObjectFactory.getResource(id);
		
		mav.addObject(VIEW_VOCABULARY.PARAMETER_RESOURCE, resource);
		mav.setViewName(VIEW_VOCABULARY.VIEW_RESOURCE_SMALL);
		return mav;
	}
	
	/**
	 * This method handles requests that deal with the internal GroupMe! search.
	 * 
	 * @param request the HTTP request
	 * @param response the HTTP response
	 * 
	 * @return the Spring {@link ModelAndView} object which includes also object that
	 * can be accessed by the view (e.g. the actual search result list...)
	 */
	public ModelAndView search(HttpServletRequest request, HttpServletResponse response){
		ModelAndView mav = new ModelAndView();
		
		String keywords = getStringValue(request, VIEW_VOCABULARY.PARAMETER_KEYWORD);
		keywords = keywords.replaceAll("_", " ");
		String searchFor = getStringValue(request, VIEW_VOCABULARY.PARAMETER_SEARCH_FOR);
		String filterType = getStringValue(request, VIEW_VOCABULARY.PARAMETER_FILTER_RESOURCETYPE);
		String pageID = getStringValue(request, "pageID");
		mav.addObject(VIEW_VOCABULARY.PARAMETER_KEYWORD, keywords);
		mav.addObject(VIEW_VOCABULARY.PARAMETER_TAG, ObjectFactory.getTagIfExist(keywords));
		mav.addObject(VIEW_VOCABULARY.PARAMETER_FILTER_RESOURCETYPE, filterType);
		boolean keywordsAndConnected = Boolean.parseBoolean(getStringValue(request, VIEW_VOCABULARY.PARAMETER_SEARCH_FOR_ALL));
		
		//begin groupme-logging:
		boolean freesearch = new Boolean(getStringValue(request, VIEW_VOCABULARY.PARAMETER_FREE_SEARCH));
		User user =  ((UserManager) applicationContext.getBean(VIEW_VOCABULARY.USER_MANGER, UserManager.class)).getUser();
		String sessionID = request.getSession().getId();
		
		if(freesearch){
			glogger.logSearch(user, sessionID, ActionIdentifier.FREE_SEARCH, keywords);
		}else{
			glogger.logSearch(user, sessionID, ActionIdentifier.TAG_SEARCH, keywords);
		}
		//end groupme-logging
		
		SearchManager searchManager = (SearchManager) applicationContext.getBean(VIEW_VOCABULARY.GROUPME_SEARCH_MANGER, SearchManager.class);
		//searchManager.resetSearchHistory();
		
		if(VIEW_VOCABULARY.SEARCH_FOR_GROUPS.equals(searchFor)){
			try {
				
				if(pageID == null){//start new search
					
					searchManager.resetSearchHistory();
					searchManager.setLastSearchFor(keywords);
					List<Group> suitingGroups = searchManager.searchForGroups(keywords, keywordsAndConnected);
					searchManager.setSuitingGroups(suitingGroups);
					mav.addObject(VIEW_VOCABULARY.SEARCH_RESULT_LIST, suitingGroups);
					
					logger.info(suitingGroups == null ? "No Groups were found while searching for " + keywords : "Number of results (while searching for " + keywords + "): " + suitingGroups.size());
					
				}else {//return cached search result
					
					//List<Group> suitingGroups = searchManager.searchForGroups(keywords, keywordsAndConnected);
					List<Group> suitingGroups = searchManager.getSuitingGroups();
					mav.addObject(VIEW_VOCABULARY.SEARCH_RESULT_LIST, suitingGroups);
					
					logger.info(suitingGroups == null ? "No Groups were found while searching for " + keywords : "Number of results (while searching for " + keywords + "): " + suitingGroups.size());
				}
				
			} catch (SearchEngineException e) {
				e.printStackTrace();
				mav.addObject(VIEW_VOCABULARY.STATUS_SEARCH, "An error occured during the GroupMe! search. Please contact us if this problem persists.");
			}
		}else if(VIEW_VOCABULARY.SEARCH_FOR_RESOURCES.equals(searchFor)){
			try {
				
				if(pageID == null){//start new search}
					
					searchManager.resetSearchHistory();
					searchManager.setLastSearchFor(keywords);
					List<Resource> suitingResources = searchManager.searchForResources(keywords, keywordsAndConnected, true);
					searchManager.setSuitingResources(suitingResources);
					mav.addObject(VIEW_VOCABULARY.SEARCH_RESULT_LIST, suitingResources);
					if(filterType != null){
						//List<Resource> filteredList = filterResultwithType(suitingResources, filterType);
						List<Resource> filteredList = filterResultwithType(searchManager.getSuitingResources(), filterType);
						searchManager.setSuitingFilteredResources(filteredList);
						mav.addObject(VIEW_VOCABULARY.FILTERED_SEARCH_RESULT_LIST, filteredList);
						logger.info("Filter the search result with resource type: " + filterType);
					}
					
					logger.info(suitingResources == null ? "No resources were found while searching for " + keywords : "Number of results (while searching for " + keywords + "): " + suitingResources.size());

				}else {
					
					List<Resource> suitingResources = searchManager.getSuitingResources();
					mav.addObject(VIEW_VOCABULARY.SEARCH_RESULT_LIST, suitingResources);
					if(filterType != null){
						//List<Resource> filteredList = filterResultwithType(suitingResources, filterType);
						List<Resource> filteredList = searchManager.getSuitingFilteredResources();
						//searchManager.setSuitingFilteredResources(filteredList);
						mav.addObject(VIEW_VOCABULARY.FILTERED_SEARCH_RESULT_LIST, filteredList);
						logger.info("Filter the search result with resource type: " + filterType);
					}
					
				}
				
				
				
			} catch (SearchEngineException e) {
				e.printStackTrace();
				mav.addObject(VIEW_VOCABULARY.STATUS_SEARCH, "An error occured during the GroupMe! search. Please contact us if this problem persists.");
			}
		}else { //search for groups and resources:
			try {
				
				if(pageID == null){//start new search
					
					searchManager.resetSearchHistory();
					searchManager.setLastSearchFor(keywords);
					List<Resource> suitingResources = searchManager.searchForResources(keywords, keywordsAndConnected, false);
					searchManager.setSuitingResources(suitingResources);
					mav.addObject(VIEW_VOCABULARY.SEARCH_RESULT_LIST, suitingResources);
					//List<Group> suitingGroups = searchManager.searchForGroups(keywords, keywordsAndConnected);
					//mav.addObject(VIEW_VOCABULARY.SEARCH_RESULT_GROUPS, suitingGroups);
					
					if(filterType != null){
						List<Resource> filteredList = filterResultwithType(searchManager.getSuitingResources(), filterType);
						searchManager.setSuitingFilteredResources(filteredList);
						mav.addObject(VIEW_VOCABULARY.FILTERED_SEARCH_RESULT_LIST, filteredList);
						logger.info("Filter the search result with resource type: " + filterType);
					}
					
					//logger.info(suitingGroups == null ? "No Groups were found while searching for " + keywords : "Number of results (while searching for " + keywords + "): " + suitingGroups.size());
					logger.info(suitingResources == null ? "No resources or groups were found while searching for " + keywords : "Number of results (while searching for " + keywords + "): " + suitingResources.size());
				}else {
					List<Resource> suitingResources = searchManager.getSuitingResources();
					mav.addObject(VIEW_VOCABULARY.SEARCH_RESULT_LIST, suitingResources);
					if(filterType != null){
						//List<Resource> filteredList = filterResultwithType(suitingResources, filterType);
						List<Resource> filteredList = searchManager.getSuitingFilteredResources();
						//searchManager.setSuitingFilteredResources(filteredList);
						mav.addObject(VIEW_VOCABULARY.FILTERED_SEARCH_RESULT_LIST, filteredList);
						logger.info("Filter the search result with resource type: " + filterType);
					}
				}
				
				
			} catch (SearchEngineException e) {
				e.printStackTrace();
				mav.addObject(VIEW_VOCABULARY.STATUS_SEARCH, "An error occured during the GroupMe! search. Please contact us if this problem persists.");
			}
		}
		return mav;
	}
	
	/**
	 * Returns a ModelAndView that contains a list of popular groups
	 * @param request the HTTP requests which possibly specifies on which basis the popular resourcs should be computed
	 * @return a ModelAndView that contains a list of popular groups
	 */
	private ModelAndView getPopularGroups(HttpServletRequest request){
		ModelAndView mav = new ModelAndView();
		mav.addObject(VIEW_VOCABULARY.SEARCH_RESULT_LIST, SearchUtility.getMostPopularGroupsAccordingToClickFrequency(SEARCH_CONFIGURATION.NUMBER_OF_GROUPS_TO_BE_LISTED, SEARCH_CONFIGURATION.NUMBER_OF_MINIMUM_RESOURCES_IN_GROUPS_FOR_STARTPAGE));
		return mav;
	}
	
	/**
	 * Returns a ModelAndView that contains a list of random groups
	 * @param request the HTTP requests which possibly specifies on which basis the random resources should be computed
	 * @return a ModelAndView that contains a list of random groups
	 */
	private ModelAndView getRandomGroups(HttpServletRequest request){
		ModelAndView mav = new ModelAndView();
		mav.addObject(VIEW_VOCABULARY.SEARCH_RESULT_LIST, SearchUtility.getRandomGroups(SEARCH_CONFIGURATION.NUMBER_OF_GROUPS_TO_BE_LISTED, SEARCH_CONFIGURATION.NUMBER_OF_MINIMUM_RESOURCES_IN_GROUPS_FOR_STARTPAGE));
		return mav;
	}
	
	/**
	 * Returns a ModelAndView that contains a list of latest groups
	 * @param request the HTTP requests which possibly specifies on which basis the random resources should be computed
	 * @return a ModelAndView that contains a list of latest groups
	 */
	private ModelAndView getLatestGroups(HttpServletRequest request, int startPosition){
		ModelAndView mav = new ModelAndView();
		mav.addObject(VIEW_VOCABULARY.SEARCH_RESULT_LIST, SearchUtility.getLatestGroups(startPosition, SEARCH_CONFIGURATION.NUMBER_OF_GROUPS_TO_BE_LISTED, SEARCH_CONFIGURATION.NUMBER_OF_MINIMUM_RESOURCES_IN_GROUPS_FOR_STARTPAGE));
		return mav;
	}
	
	/**
	 * Returns the number of all latest groups
	 * @return the number of all latest groups
	 */
	private int getNumberOfLatestGroups(){
		return numberOfLatestGroups;
	}
	
	/**
	 * Sets the number of all latest groups
	 */
	private void setNumberOfLatestGroups(){
		this.numberOfLatestGroups = SearchUtility.getNumberofLatestGroups(SEARCH_CONFIGURATION.NUMBER_OF_MINIMUM_RESOURCES_IN_GROUPS_FOR_STARTPAGE);
	}
	
	/**
     * Very small helper function to extract a given parameter or attribute from a HttpServletRequest.
     * @param request the {@link HttpServletRequest} object
     * @param parameter the parameter
     * @return the value of the parameter as a String
     */
    protected String getStringValue(HttpServletRequest request, String parameter){
    	String value = request.getParameter(parameter);
    	if(value == null && request.getAttribute(parameter) != null){ //normally this is not accessed but we do our best to extract the value.. 
    		value = request.getAttribute(parameter).toString();
    	}
    	return value;
    }
    
    /**
     * Small helper function that filters a result list to contain only a certain resource type
     * @param resultList the result list to be filtered
     * @param type the resource type to filter
     * @return the filtered result list
     */
    private List<Resource> filterResultwithType(List<Resource> resultList, String type){
    	List<Resource> result = resultList;
    	List<Resource> filteredList = new ArrayList<Resource>();
    	
    	Iterator<Resource> resultIterator = result.iterator();
    	
    	if(type.equals(MODEL_CONSTANTS.RESOURCE_TYPE_MULTIMEDIA)){
    		while(resultIterator.hasNext()){
        		Resource nextResource = resultIterator.next();
        		String resourceType = nextResource.getType();
        		if(resourceType.equals(MODEL_CONSTANTS.RESOURCE_TYPE_AUDIO) || resourceType.equals(MODEL_CONSTANTS.RESOURCE_TYPE_VIDEO) || resourceType.equals(MODEL_CONSTANTS.RESOURCE_TYPE_SHOCKWAVE)){
        			filteredList.add(nextResource);	
        		}
        	}
    	}else{
    		while(resultIterator.hasNext()){
        		Resource nextResource = resultIterator.next();
        		if(nextResource.getType().equals(type))
        			filteredList.add(nextResource);	
        	}
    	}
    	return filteredList;
    }
}
